#ifndef MQSERVER_MQ_BINDING_HPP_
#define MQSERVER_MQ_BINDING_HPP_
#include "../mqcomm/mq_logger.hpp"
#include "../mqcomm/mq_helper.hpp"
#include <iostream>
#include <unordered_map>
#include <memory>
#include <mutex>
#include <cassert>

namespace mqserver
{
class Binding;
class BindingManager;
using BindingPtr = std::shared_ptr<Binding>;
using BindingManagerPtr = std::shared_ptr<BindingManager>;

// Exchange : MsgQueue = 1 : n
// MsgQueueBindingMap: msgqueue_name --> Binding
// BindingMap: exchange_name --> MsgQueueBindingMap == n * (msgqueue_name --> Binding)
using MsgQueueBindingMap = std::unordered_map<std::string, BindingPtr>; 
using BindingMap = std::unordered_map<std::string, MsgQueueBindingMap>;

class Binding
{
public:
    Binding() = default;
    Binding(const std::string& exchange_name, 
            const std::string& msgqueue_name, 
            const std::string& binding_key, bool durable)
        : exchange_name_(exchange_name), msgqueue_name_(msgqueue_name)
        , binding_key_(binding_key), durable_(durable)
    {}
public:
    std::string exchange_name_;
    std::string msgqueue_name_;
    std::string binding_key_;
    bool durable_;      // exchange.durable && msgqueue.durable
};

class BindingMapper
{
public:
    BindingMapper(const std::string dbfile) : sql_helper_(dbfile) 
    {
        const std::string path = helper::FileHelper::parentDirectory(dbfile);
        helper::FileHelper::createDirectory(path);
        assert(sql_helper_.open());
        createTable();
    }
    ~BindingMapper() { sql_helper_.close(); }

    void createTable()
    {
        const std::string create_table_sql =
            "create table if not exists binding_table( \
            exchange_name varchar(32), \
            msgqueue_name varchar(32), \
            binding_key varchar(128), \
            durable bool);";

        bool ret = sql_helper_.exec(create_table_sql, nullptr, nullptr);
        if(ret == false) 
        {
            LOG_FATAL << "create binding_table fail!\n";
            abort();
        }
    }
    void removeTable()
    {
        const std::string drop_table_sql = "drop table if exists binding_table;";
        bool ret = sql_helper_.exec(drop_table_sql, nullptr, nullptr);
        if(ret == false) 
        {
            LOG_FATAL << "drop binding_table fail!\n";
            abort();
        }
    }
    bool insert(const BindingPtr& binding)
    {
        const std::string insert = "insert into binding_table values('%s', '%s', '%s', %d);";
        char insert_sql[4096] = {0};
        sprintf(insert_sql, insert.c_str(), binding->exchange_name_.c_str(),
                binding->msgqueue_name_.c_str(), binding->binding_key_.c_str(), binding->durable_);
        
        return sql_helper_.exec(insert_sql, nullptr, nullptr);
    }
    bool remove(const std::string& ename, const std::string& qname)
    {
        const std::string delete_sql = "delete from binding_table \
                                        where exchange_name = '" + ename + "' and \
                                        msgqueue_name = '" + qname + "';";
        return sql_helper_.exec(delete_sql, nullptr, nullptr);
    }
    bool removeExchangeBindings(const std::string& ename)
    {
        const std::string delete_sql = "delete from binding_table \
                                        where exchange_name = '" + ename + "';";
        return sql_helper_.exec(delete_sql, nullptr, nullptr);
    }
    bool removeMsgQueueBindings(const std::string& qname)
    {
        const std::string delete_sql = "delete from binding_table \
                                        where msgqueue_name = '" + qname + "';";
        return sql_helper_.exec(delete_sql, nullptr, nullptr);
    }
    BindingMap recovery()
    {
        BindingMap result;
        const std::string select_sql = "select exchange_name, msgqueue_name, binding_key, durable \
                                        from binding_table;";
        sql_helper_.exec(select_sql, selectCallback, (void*)&result);
        return result;
    }
private:
    static int selectCallback(void* arg, int col_count, char** row, char** fields_name)
    {
        BindingMap* result = static_cast<BindingMap*>(arg);
        BindingPtr binding = std::make_shared<Binding>(row[0], row[1], row[2], (bool)(std::stoi(row[3])));
        // 通过 exchange_name获取 MsgQueueBindingMap 不存在则创建
        MsgQueueBindingMap &mq_binding = (*result)[binding->exchange_name_];
        mq_binding.emplace(binding->msgqueue_name_, binding);
        return 0;
    }
private:
    helper::SqliteHelper sql_helper_;
};

class BindingManager
{
public:
    BindingManager(const std::string& dbfile)
        : mapper_(dbfile) { mapper_.recovery(); }
    
    bool bind(const std::string& ename, const std::string& qname, 
            const std::string& binding_key, bool durable)
    {
        std::lock_guard<std::mutex> lock(mtx_);
        if(existsNoLock(ename, qname)) return true;
        BindingPtr binding = std::make_shared<Binding>(ename, qname, binding_key, durable);
        if(durable)
        {
            bool ret = mapper_.insert(binding);
            if(!ret) return false;
        }
        bindings_[ename].emplace(qname, binding);
        return true;
    }

    void unbind(const std::string &ename, const std::string &qname)
    {
        std::lock_guard<std::mutex> lock(mtx_);
        if(!existsNoLock(ename, qname)) return;
        if(durableStatus(ename, qname)) mapper_.remove(ename, qname);
        bindings_.erase(ename);
    }

    void removeExchangeBindings(const std::string &ename)
    {
        std::lock_guard<std::mutex> lock(mtx_); 
        mapper_.removeExchangeBindings(ename);
        bindings_.erase(ename);
    }

    void removeMsgQueueBindings(const std::string &qname)
    {
        std::lock_guard<std::mutex> lock(mtx_); 
        mapper_.removeMsgQueueBindings(qname);
        for(auto it = bindings_.begin(); it != bindings_.end(); ++it)
        {
            if(durableStatus(it->first, qname))
                it->second.erase(qname);
        }
    }

    MsgQueueBindingMap getExchangeBindings(const std::string &ename)
    {
        std::lock_guard<std::mutex> lock(mtx_); 
        return bindings_.find(ename) != bindings_.end() ? bindings_[ename] : MsgQueueBindingMap();
    }

    BindingPtr getBinding(const std::string &ename, const std::string &qname)       // 判断指定绑定信息是否存在
    {
        std::lock_guard<std::mutex> lock(mtx_); 
        return durableStatus(ename, qname) ? bindings_[ename][qname] : BindingPtr();
    }

    bool exists(const std::string &ename, const std::string &qname)
    {
        std::lock_guard<std::mutex> lock(mtx_); 
        auto it = bindings_.find(ename);
        return (it != bindings_.end() && it->second.find(qname) != it->second.end());
    }

    size_t size()
    {
        size_t total_size = 0;
        std::lock_guard<std::mutex> lock(mtx_);   
        for(auto it = bindings_.begin(); it != bindings_.end(); ++it)
            total_size += it->second.size();
        return total_size;
    }

    void clear()
    {
        std::lock_guard<std::mutex> lock(mtx_);   
        mapper_.removeTable();
        bindings_.clear();
    }
private:
    bool durableStatus(const std::string &ename, const std::string &qname)  
    { 
        return bindings_[ename][qname]->durable_;
    }
    bool existsNoLock(const std::string &ename, const std::string &qname)
    {
        auto it = bindings_.find(ename);
        return (it != bindings_.end() && it->second.find(qname) != it->second.end());
    }

private:
    std::mutex mtx_;
    BindingMapper mapper_;
    BindingMap bindings_;
};

}

#endif